﻿using Microsoft.EntityFrameworkCore;
using mozhi.smarterp.Finance.Accounts.Repository;
using mozhi.smarterp.Finance.EntityFrameworkCore;
using mozhi.smarterp.Finance.Enums;
using mozhi.smarterp.Finance.Views;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Guids;

namespace mozhi.smarterp.Finance.Accounts
{
    public class AccountRepository : EfCoreRepository<FinanceDbContext, Account , Guid>, IAccountRepository
    {
        private IGuidGenerator _guidGenerator;

        public AccountRepository(IDbContextProvider<FinanceDbContext> dbContextProvider, IGuidGenerator guidGenerator) : base(dbContextProvider)
        {
            _guidGenerator = guidGenerator; ;
        }

        public async Task<int> GetCountAsync(string keyword, string code, string name, AccountingStandardType? standardType, Guid? excludeId, Guid? parentId, CancellationToken cancellationToken = default(CancellationToken))
        {
            var dbContext = await GetDbContextAsync();

            int count = await dbContext.Set<Account>().AsNoTracking()
                .WhereIf(standardType.HasValue, x => x.AccountStandardType == standardType)
                .WhereIf(!keyword.IsNullOrEmpty(), x => x.Code.Contains(keyword) || x.Name.Contains(keyword))
                .WhereIf(!code.IsNullOrEmpty(), x => x.Code == code)
                .WhereIf(!name.IsNullOrEmpty(), x => x.Code == name)
                .WhereIf(excludeId.HasValue, x => x.Id != excludeId)
                .WhereIf(parentId.HasValue, x => x.ParentId == parentId)
                .CountAsync(cancellationToken);

            return count;
        }

        public async Task<List<Account>> GetPageListAsync(string keyword, string code, string name, AccountingStandardType? standardType, Guid? excludeId, Guid? parentId, int skipCount, int maxResultCount, string sorting, bool includeDetails = false, CancellationToken cancellationToken = default(CancellationToken))
        {
            var dbContext = await GetDbContextAsync();

            var query = includeDetails ? dbContext.Set<Account>().AsNoTracking().Include(x=>x.DimensionItems) : dbContext.Set<Account>().AsNoTracking();

            var accounts = await query
                .WhereIf(standardType.HasValue, x => x.AccountStandardType == standardType)
                .WhereIf(!keyword.IsNullOrEmpty(), x => x.Code.Contains(keyword) || x.Name.Contains(keyword))
                .WhereIf(!code.IsNullOrEmpty(), x => x.Code == code)
                .WhereIf(!name.IsNullOrEmpty(), x => x.Code == name)
                .WhereIf(excludeId.HasValue, x => x.Id != excludeId)
                .WhereIf(parentId.HasValue, x => x.ParentId == parentId)
                .OrderByIf<Account, IQueryable<Account>>(!sorting.IsNullOrWhiteSpace(), sorting)
                .PageBy(skipCount, maxResultCount).ToListAsync(GetCancellationToken(cancellationToken));

            return accounts;
        }


        public override async Task<Account> GetAsync(Guid id, bool includeDetails = true, CancellationToken cancellationToken = default(CancellationToken))
        {
            var dbContext = await GetDbContextAsync();

            var account = includeDetails == true ? await dbContext.Set<Account>().AsNoTracking().Include(x => x.DimensionItems).FirstOrDefaultAsync(x => x.Id == id , cancellationToken) :
                await dbContext.Set<Account>().FirstOrDefaultAsync(x => x.Id == id , cancellationToken);

            return account;
        }

        public async Task<List<Account>> GetDefaultAccountsAsync(AccountingStandardType accountingStandard , CancellationToken cancellationToken = default(CancellationToken))
        {
            var dbContext = await GetDbContextAsync();

            var result = new List<Account>();

            var defaultAccounts = await dbContext.Set<vDefaultAccount>().AsNoTracking().Where(x => x.AccountStandard == accountingStandard).ToListAsync(cancellationToken);

            if (defaultAccounts.Count == 0)
                return result;


            foreach (var defaultAcount in defaultAccounts)
            {
                var account = new Account(_guidGenerator.Create(), defaultAcount.Code, defaultAcount.Name, defaultAcount.AccountType, defaultAcount.Direction);

                account.SetAccountStandard(accountingStandard);

                account.SetDirection(defaultAcount.Direction);

                account.SetStatus(true);

                result.Add(account);
            }

            foreach (var defaultAccount in defaultAccounts)
            {
                var account = result.First(x => x.Code == defaultAccount.Code);

                var matchParent = result.FirstOrDefault(x => x.Code == defaultAccount.ParentCode);

                if (matchParent != null)
                {
                    if (matchParent.FullName.IsNullOrEmpty())
                        matchParent.ChangeBaseInfo(matchParent.Code, matchParent.Name, matchParent.Code + " " + matchParent.Name);
                    account.SetParent(matchParent);
                }
                else if (account.FullName.IsNullOrEmpty())
                    account.ChangeBaseInfo(account.Code, account.Name, account.Code + " " + account.Name);


                //设置是否为叶子结点
                var isLeaf = !defaultAccounts.Exists(x => x.ParentCode == defaultAccount.Code);

                account.SetLevel(isLeaf);
            }

            return result.OrderBy(x => x.Code).ToList();
        }

        public async Task<List<Account>> GetAccountIncludeParentAsync(Guid id, bool includeDetails = false, CancellationToken cancellationToken = default(CancellationToken))
        {
            var result = new List<Account>();

            var dbContext = await GetDbContextAsync();

            var query = includeDetails == true ? dbContext.Accounts.Include(x => x.DimensionItems).AsNoTracking().AsQueryable() : dbContext.Accounts.AsNoTracking().AsQueryable();

            var account = await query.FirstOrDefaultAsync(x => x.Id == id);

            if (account == null)
                return result;

            //获取它的父级科目
            var parents = await GetAccountParentsAsync(account, query);

            result.Add(account);
            result.AddRange(parents);

            return result;
        }

        public async Task<List<Account>> GetAccountIncludeChildrenAsync(Guid id, bool includeDetails = false , CancellationToken cancellationToken = default(CancellationToken))
        {
            var result = new List<Account>();

            var dbContext = await GetDbContextAsync();

            var query = includeDetails == true ? dbContext.Accounts.Include(x => x.DimensionItems).AsNoTracking().AsQueryable() : dbContext.Accounts.AsNoTracking().AsQueryable();

            var account = await query.FirstOrDefaultAsync(x => x.Id == id);

            if (account == null)
                return result;

            var children = await GetAccountChildrenAsync(account, query);

            result.AddRange(children);

            return result;
        }

        #region
        private async Task<List<Account>> GetAccountParentsAsync(Account child , IQueryable<Account> query)
        {
            var result = new List<Account> ();

            if (!child.ParentId.HasValue)
                return result;

            var parent = await query.FirstOrDefaultAsync(x=>x.Id == child.ParentId.Value);

            if (parent == null) return result;

            result.Add(parent);

            var children = await GetAccountParentsAsync(parent, query);

            result.AddRange(children);

            return result;
        }

        private async Task<List<Account>> GetAccountChildrenAsync(Account parent, IQueryable<Account> query)
        {
            var result = new List<Account>();

            var children = await query.Where(x=>x.ParentId == parent.Id).ToListAsync();

            //表示没有子级科目
            if (children.Count==0) return result;

            result.AddRange(children);

            foreach(var child in children)
            {
                var tempChildren = await GetAccountChildrenAsync(child , query);

                result.AddRange(tempChildren);
            }

            return result;
        }
        #endregion
    }
}
